package app.server.project.main.service.service.rank;

import app.server.project.core.constants.Def;
import app.server.project.main.service.bo.event.LvEvent;
import app.server.project.main.service.bo.event.RankEvent;
import app.server.project.main.service.db.dao.cfg.RaceCfgDao;
import app.server.project.main.service.db.dao.cfg.RankCfgDao;
import app.server.project.main.service.db.dao.user.UserExtLock;
import app.server.project.main.service.db.dao.user.UserInfoDao;
import app.server.project.main.service.db.dao.user.UserRankDao;
import app.server.project.main.service.db.entity.cfg.RankCfgEntity;
import app.server.project.main.service.db.entity.user.UserRankEntity;
import app.server.project.main.service.dto.rank.RankDto;
import app.server.project.main.service.dto.rank.RankResp;
import app.server.project.main.service.service.LockService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

@Data
@Slf4j
public abstract class RankBase {


    private final static int MAX = 50000;
    private final static int LAZY_RATE = 1000;
    private final static int DB_MAX = 300;
    private final static int TimeCache = 1;
    /**
     * 上榜人数
     */
    private int rankNum = 100;
    /**
     * 奖励名次
     */
    private int rewardNum = 1;
    private int rankId;
    private ConcurrentLinkedQueue<RankEvent> queue = new ConcurrentLinkedQueue<RankEvent>();
    private List<RankDto> rankDtoList = new ArrayList<>();
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private TaskScheduler taskScheduler;
    @Autowired
    private RankCfgDao rankCfgDao;
    @Autowired
    private UserRankDao userRankDao;
    @Autowired
    private RaceCfgDao raceCfgDao;
    @Autowired
    private UserExtLock userExtLock;
    @Autowired
    private LockService lockService;

    private int raceId;

    private Cache<Long, UserRankEntity> cache = Caffeine.newBuilder()
            .expireAfterAccess(TimeCache, TimeUnit.HOURS)
            .build();

    /**
     * 刷新结算时间
     *
     * @param cfgEntity
     */
    public abstract void flushNextTime(RankCfgEntity cfgEntity);

    /**
     * 结算逻辑
     */
    public abstract void doSettlement();

    public void init(int rankId, int rewardNum, int rankNum, int raceId) {
        this.rankId = rankId;
        this.raceId = raceId;
        this.rewardNum = rewardNum;
        this.rankNum = rankNum;
        this.initRank();
        this.startTask();
    }

    private void initRank() {
        List<RankDto> tempRank = new ArrayList<>();
        var list = this.userRankDao.getRankByRankId(this.rankId, this.rankNum);
        var raceCfg = this.raceCfgDao.getOne(this.raceId);
        int pos = 1;
        for (int i = 0; i < list.size(); i++) {
            var data = this.userInfoDao.getUserInfoById(list.get(i).getUserId());
            var dto = RankDto.of(data, list.get(i).getVal(), raceCfg.getRate());
            dto.setPos(pos);
            pos++;
            tempRank.add(dto);
        }
        this.rankDtoList = tempRank;
    }


    /**
     * 开启结算任务
     */
    private void startTask() {
        RankCfgEntity cfgEntity = this.rankCfgDao.getRankById(this.rankId);
        if (cfgEntity.getNextSettlementTime() == null) {
            this.flushNextTime(cfgEntity);
        }
        log.info("排行榜id:{},下次结算时间:{}", this.rankId, cfgEntity.getNextSettlementTime());
        var instant = Instant.now();
        var diff = cfgEntity.getNextSettlementTime().getTime() - new Date().getTime();
        instant = instant.plusMillis(diff);
        this.taskScheduler.schedule(() -> {
            this.doTask(cfgEntity);
        }, instant);
    }

    private synchronized void doTask(RankCfgEntity cfgEntity) {
        try {
            this.cache.invalidateAll();
            this.doSettlement();
        } catch (Exception e) {
            log.error("排行榜id:{},结算错误:{}", this.rankId, e);
        }
        log.info("排行榜id:{}结算成功", this.rankId);
        this.flushNextTime(cfgEntity);
        this.initRank();
        this.startTask();
    }

    /**
     * 读取排行榜
     *
     * @return
     */
    public RankResp getRank(long targetUserId) {
        var my = this.rankDtoList.stream().filter(v -> v.getUserId() == targetUserId).findFirst();
        if (my.isPresent()) {
            return RankResp.of(this.rankDtoList, my.get().getPos(), String.valueOf(my.get().getVal()), String.valueOf(my.get().getRate()));
        } else {
            var cache = this.getUserRankData(targetUserId, this.rankId);
            var race = this.raceCfgDao.getOne(this.raceId);
            return RankResp.of(this.rankDtoList, Def.Zero, String.valueOf(cache.getVal()), String.valueOf(race.getRate()));
        }
    }

    /**
     * 拉取数据
     *
     * @param userId
     * @param rankId
     * @return
     */
    public UserRankEntity getUserRankData(long userId, int rankId) {
        var ret = this.cache.getIfPresent(userId);
        if (Objects.nonNull(ret)) return ret;
        var lock = this.lockService.getLock(userId);
        synchronized (lock) {
            ret = this.cache.getIfPresent(userId);
            if (Objects.nonNull(ret)) return ret;
            ret = this.userRankDao.getByIds(userId, rankId);
            this.cache.put(userId, ret);
            return ret;
        }
    }

    /**
     * 添加排行榜变更值
     *
     * @param event
     */
    public void addRankVal(RankEvent event) {
        this.queue.offer(event);
    }

    @Async(Def.CommonPool)
    @EventListener(LvEvent.class)
    public void flushLv(LvEvent event) {
        this.rankDtoList.forEach(v -> {
            if (v.getUserId() == event.getUserId()) {
                var userinfo = this.userInfoDao.getUserInfoById(v.getUserId());
                v.setLv(userinfo.getLv());
            }
        });
    }

    /**
     * 同步数据库
     *
     * @return
     */
    private List<RankEvent> flushDb() {
        List<RankEvent> rankEvents = new ArrayList<>();
        int max = DB_MAX;
        while (max > 0) {
            var event = this.queue.poll();
            if (event == null) break;
            var db = this.getUserRankData(event.getUserId(), this.rankId);

            var userExtEntity = this.userExtLock.getUserExtByUserExtId(event.getUserId());
            if (userExtEntity.getInnerType() == Def.True) {
                db.setVal(Def.Zero);
            } else {
                db.addVal(event.getVal());
            }
            event.setSumVal(db.getVal());
            rankEvents.add(event);
            max--;
        }
        return rankEvents;
    }

    /**
     * 排序
     *
     * @param events
     */
    private void sort(List<RankEvent> events) {
        if (events.isEmpty()) return;
        var raceCfg = this.raceCfgDao.getOne(this.raceId);
        events.forEach(v -> {
            var userInfo = this.userInfoDao.getUserInfoById(v.getUserId());
            //先移除
            this.rankDtoList.removeIf(p -> p.getUserId() == v.getUserId());
            //再添加
            this.rankDtoList.add(RankDto.of(userInfo, v.getSumVal(), raceCfg.getRate()));
        });
        this.rankDtoList.removeIf(p -> p.getVal() == Def.Zero);
        this.rankDtoList.sort(Comparator.comparingLong(RankDto::getVal).reversed());
        for (int i = 0; i < this.rankDtoList.size(); i++) {
            this.rankDtoList.get(i).setPos(i + 1);
        }

        if (this.rankDtoList.size() > this.rankNum) {
            this.rankDtoList = this.rankDtoList.subList(Def.Zero, this.rankNum);
        }

    }

    @Scheduled(fixedDelay = LAZY_RATE)
    public synchronized void consume() {
        var events = this.flushDb();
        this.sort(events);
    }
}
